import simpy
import logging
from .base import BasePhy
from .radio_channel import RadioChannel
from .record import recorder

logger = logging.getLogger('RadioPhy')

class RadioPhy(BasePhy):

    def __init__(self, env, clock, front_gap, back_gap, slot_length, frame_length):
        self.env = env
        self.clock = clock
        self.front_gap_delay = front_gap * clock.get_period()
        self.back_gap_delay = back_gap * clock.get_period()
        self.tx_delay = (slot_length - (front_gap + back_gap)) * clock.get_period()
        self.slot_length = slot_length
        self.frame_length = frame_length
        self.timeslot_event = simpy.Event(self.env)
        self.receive_event = simpy.Event(self.env)
        self.curr_slot = 0
        self.curr_frame = 0
        self.rx_start_clk = 0
        self.rx_end_clk = 0
        self.tx_start_clk = 0
        self.tx_end_clk = 0
        self.is_tx_valid = False
        self.is_rx_valid = False
        self.is_synced = False

    def set_tx_power(self, tx_power):
        self.tx_power = tx_power
        
    def set_rx_sensitivity(self, sensitivity):
        self.rx_sensitivity = sensitivity

    def set_tx_freq(self, freq):
        self.tx_freq = freq

    def set_channel(self, channel:RadioChannel):
        self.channel = channel

    def set_node(self, node):
        self.node = node

    def wait_timeslot(self):
        return self.timeslot_event

    def wait_receive(self):
        return self.receive_event

    def fs(self):
        s = (self.clock.clock() // self.slot_length) % self.frame_length
        f = self.clock.clock() // (self.slot_length * self.frame_length)
        return (f, s)

    def start(self):

        self.env.process(self.clock.start())
        
        while True:

            yield self.clock.wait_clock()
            clk = self.clock.clock()

            # Check timeslot event
            if clk % self.slot_length == 0:
                fs = self.fs()
                logger.debug(f'{self.env.now:.6f} {self.node.id} {fs}')
                
                # Check invalid rx
                if self.is_rx_valid:
                    logger.error(f'{self.env.now:.6f} {self.node.id} invalid rx')
                    self.is_rx_valid = False

                self.timeslot_event.succeed()
                self.timeslot_event = simpy.Event(self.env)

    def tx(self, data):

        # Front gap delay
        yield self.env.timeout(self.front_gap_delay)
        
        # Record
        metadata = recorder.record_packet_send(self.node, self.env.now, data['tag'])
        
        # Send to channel
        ts = self.clock.clock()
        logger.debug(f'{self.env.now:.6f} {self.node.id} tx ts:{ts}')
        self.is_tx_valid = True
        frame = {'ts':ts}
        self.channel.transmit(self.node, frame, self.tx_freq, self.tx_power, None)
        yield self.env.timeout(self.tx_delay)

        ts = self.clock.clock()
        frame = {'ts':ts, 'data': data}
        self.channel.transmit(self.node, frame, self.tx_freq, self.tx_power, metadata)
        self.is_tx_valid = False

        if self.is_synced == False:
            self.is_synced = True

    def rx(self, sender, frame, rssi, metadata):

        if rssi < self.rx_sensitivity:
            return

        ts = frame['ts']
        if 'data' not in frame:
            # rx start
            if self.is_synced == False:
                logger.info(f'{self.env.now:.6f} {self.node.id} first sync from {sender.id} clk:{ts}')
                self.is_synced = True
                self.clock.set_clock(ts)
            tr = self.clock.clock()
            self.is_rx_valid = True
            self.rx_start_clk = tr
            self.tx_start_clk = ts
            logger.debug(f'{self.env.now:.6f} {self.node.id} rx start from:{sender.id} ts:{ts} tr:{tr}')
        else:
            # rx end
            if self.is_rx_valid:
                tr = self.clock.clock()
                self.is_rx_valid = False
                self.rx_end_clk = tr
                self.tx_end_clk = ts
                if self.rx_end_clk - self.rx_start_clk > self.slot_length:
                    logger.error(f'{self.env.now:.6f} {self.node.id} receive error {self.rx_end_clk} {self.rx_start_clk}')
                else:
                    logger.debug(f'{self.env.now:.6f} {self.node.id} rx end from:{sender.id} ts:{ts} tr:{tr} {tr-ts}')
                    if self.tx_start_clk > self.rx_start_clk:
                        logger.info(f'{self.env.now:.6f} {self.node.id} sync to {sender.id} {self.rx_end_clk}->{self.tx_end_clk}')
                        self.clock.set_clock(ts)
                        
                # Record
                recorder.record_packet_recv(self.node, self.env.now, metadata)